package com.bookshop.controller;


import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.bookshop.aspect.exception.CustomException;
import com.bookshop.entity.request.LoginForm;
import com.bookshop.entity.response.R;
import com.bookshop.entity.response.ResponseStatus;
import com.bookshop.entity.request.SmsForm;
import com.bookshop.entity.User;
import com.bookshop.entity.wx.WxUserInfo;
import com.bookshop.sentinel.BlockHandler;
import com.bookshop.service.AsyncService;
import com.bookshop.service.UserService;
import com.bookshop.util.JWTUtil;
import com.bookshop.util.MD5Util;
import com.bookshop.util.SMSUtil;
import com.bookshop.util.WxUserUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;

/**
 * @author g2l
 * @create 2023-12-24 16:55
 */
@Slf4j
@RestController
@RequestMapping("/login")
public class LoginController {

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private UserService userService;

    @Resource
    private AsyncService asyncService;

    @Resource
    private Environment environment;

    @GetMapping("/weixinToken")
    public String weixinToken(@RequestParam String signature, @RequestParam String timestamp,
                              @RequestParam String nonce, @RequestParam String echostr){
        // todo 验证是否来自微信

        return echostr;
    }

    @GetMapping("/getWxLoginCode")
    public R getWxLoginCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 拼接微信授权的url
        String redirectUri = URLEncoder.encode(environment.getProperty("wx.domainUrl") + "/login/wxCallback", "UTF-8");
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + environment.getProperty("wx.appid")
                +  "&redirect_uri=" + redirectUri + "&response_type=code"
                + "&scope=snsapi_userinfo&state=mysql&forcePopup=true#wechat_redirect";

        // 返回对应url，前端渲染成二维码
        return R.ok().put("url", url);
    }

    @GetMapping("/wxCallback")
    public String wxCallback(@RequestParam String code, @RequestParam String state) throws IOException {
        String appid = environment.getProperty("wx.appid");
        String appSecret = environment.getProperty("wx.appsecret");

        // 微信的用户信息
        WxUserInfo userInfo = WxUserUtil.getUserInfo(code, appid, appSecret);

        if (userInfo == null){
            return "登录失败";
        }

        // 是已经注册用户
        User user = userService.getUserByUsername(userInfo.getNickname());
        if (user != null){
            return "登录成功";
        }

        // 组装用户信息
        user = new User();
        user.setPhoneNum(userInfo.getNickname());
        user.setUsername(userInfo.getNickname());
        user.setPassword(MD5Util.getPassword(userInfo.getNickname()));
        user.setPicture(userInfo.getHeadimgurl());
        user.setRoleId(1L);
        user.setCreateTime(LocalDateTime.now());
        userService.addUser(user);

        return "注册并登录成功";
    }


    @SentinelResource(value = "test", blockHandler="test", blockHandlerClass = BlockHandler.class)
    @GetMapping("/test")
    public R test(){
        return R.ok();
    }

    @SentinelResource(value = "sendRegisterSms", blockHandler="sendRegisterSms", blockHandlerClass = BlockHandler.class)
    @GetMapping("/sendRegisterSms")
    public R sendRegisterSms(@RequestParam String phoneNumber){

        // 6位数字验证码
        String captcha = SMSUtil.getCaptcha();

        // 存到redis
        redisTemplate.opsForValue().set("register" + phoneNumber, captcha);
        redisTemplate.expire("register" + phoneNumber, 60*30, TimeUnit.SECONDS);

        // 发送
        SMSUtil.sendSms(phoneNumber, captcha);

        return R.ok();
    }

    @PostMapping("/smsRegister")
    public R smsRegister(@RequestBody SmsForm smsForm){

        String phoneNumber = smsForm.getPhoneNumber();
        String captcha = smsForm.getCaptcha();

        R result = checkRegisterSms(captcha, phoneNumber);

        if (result.get("status") == ResponseStatus.SUCCESS) {
            // 注册成功，默认密码为captcha的值
            userService.registerBySms(phoneNumber, captcha);
            return R.ok().message("注册成功");
        }

        // 注册失败
        return result;
    }

    /**
     * value对应的就是sentinel流控规则的资源名
     * fallback是降级方法（对应配置文件的degrade），blockHandler是流控方法（对应配置文件的flow）
     * 两方法的参数和返回值与接口方法保持一致
     * fallback方法可以多加一个Throwable e，blockHandler方法必须多加一个BlockException exception
     * 如果流量超出流控限制时，接口会抛出 BlockException异常，
     * 而fallback方法是处理接口抛出的所有异常，blockHandler是单独只处理BlockException
     * 在面对BlockException异常时，只会调用blockHandler，如果没有配blockHandler，才会去调用fallback
     * 所以fallback是用来处理除了流控外的各种异常的，blockHandler是专门用来做流控限制的
     */
    @SentinelResource(
            value = "verifyRegisterSms",fallback="serviceDegradation",
            blockHandler="verifyRegisterSms", blockHandlerClass = BlockHandler.class)
    @GetMapping("/verifyRegisterSms")
    public R verifyRegisterSms(@RequestParam String phoneNumber, @RequestParam String captcha){

        log.info("verifyRegisterSms方法执行了");
        return checkRegisterSms(captcha, phoneNumber);
    }

    // 这个方法在这里其实不用配，仅做演示
    public R serviceDegradation(String phoneNumber, String captcha, Throwable e){
        // 把异常的详细信息打印到日志中
        log.error(e.getMessage(), e);
        throw new CustomException("服务器暂不可用，我们正在加紧赶修，请稍后");
    }

    @PostMapping("/smsLogin")
    public R smsLogin(@RequestBody SmsForm smsForm, HttpServletResponse response){

        String phoneNumber = smsForm.getPhoneNumber();
        String captcha = smsForm.getCaptcha();

        // 从redis取验证码
        String redisCaptcha = (String) redisTemplate.opsForValue().get("login" + phoneNumber);

        // 为空为null
        if (StringUtils.isBlank(redisCaptcha)){
            return R.error().message("验证码已失效");
        }

        // 登录成功
        if (redisCaptcha.equals(captcha)){
            long result = userService.smsLogin(phoneNumber);

            // 没有该用户，则直接注册，初始密码为验证码，默认权限为0
            if (result == -1L){
                userService.registerBySms(phoneNumber, captcha);
            }

            // 返回token和refreshToken
            createAuth(phoneNumber, response);

            // 这里其实应该登录后删除的，但是为了测试方便，不删除

            return R.ok().message("登录成功");
        }

        return R.error().message("验证码错误");
    }

    @SentinelResource(value = "sendLoginSms", blockHandler="sendLoginSms", blockHandlerClass = BlockHandler.class)
    @GetMapping("/sendLoginSms")
    public R sendLoginSms(@RequestParam String phoneNumber){

        // 6位数字验证码
        String captcha = SMSUtil.getCaptcha();

        // 存到redis
        redisTemplate.opsForValue().set("login" + phoneNumber, captcha);
        redisTemplate.expire("login" + phoneNumber, 60*30, TimeUnit.SECONDS);

        // 发送
        SMSUtil.sendSms(phoneNumber, captcha);

        return R.ok();
    }

    @PostMapping("/login")
    public R login(@RequestBody LoginForm loginForm, HttpServletResponse response){

        String phoneNumber = loginForm.getPhoneNumber();
        String password = loginForm.getPassword();

        Long login = userService.login(phoneNumber, password);

        if (login == -1L){
            return R.error().message("用户名或密码错误");
        }

        // 登录成功逻辑
        createAuth(phoneNumber, response);

        return R.ok().message("登录成功");
    }

    @PostMapping("/register")
    public R register(@RequestBody LoginForm loginForm, HttpServletResponse response){

        String phoneNumber = loginForm.getPhoneNumber();

        R result = checkRegisterSms(loginForm.getCaptcha(), phoneNumber);

        if (result.get("status") != ResponseStatus.SUCCESS){
            return result;
        }

        // 组装更完整的用户信息
        User user = new User();
        user.setGender(loginForm.getGender());
        user.setPhoneNum(phoneNumber);
        user.setPassword(MD5Util.getPassword(loginForm.getPassword()));
        user.setUsername(loginForm.getUsername());
        user.setRoleId(1L);
        user.setCreateTime(LocalDateTime.now());

        userService.register(user);

        return R.ok().message("注册成功");
    }

    private R checkRegisterSms(String captcha, String phoneNumber) {

        // 从redis取验证码
        String redisCaptcha = (String) redisTemplate.opsForValue().get("register" + phoneNumber);

        // 为空为null
        if (StringUtils.isBlank(redisCaptcha)){
            return R.error().message("验证码已失效");
        }

        // 验证码不对
        if (!captcha.equals(redisCaptcha)){
            return R.error().message("验证码错误");
        }

        // 用户是否已注册
        User existsUser = userService.getUserByPhoneNumber(phoneNumber);
        if (existsUser != null){
            return R.error().message("该手机号已注册");
        }

        return R.ok().message("验证成功");
    }


    // 在refreshToken有效期间，刷新token
    @GetMapping("/getNewToken")
    public R getNewToken(HttpServletRequest request, HttpServletResponse response) throws Throwable {
        // 获取两个token
        String token = request.getHeader("Authorization");
        String refreshToken = request.getHeader("refreshToken");

        // 用线程池进行优化
        // 这个接口是没有经过拦截器拦截的，所以需要在这里对token进行验证
        CompletableFuture tokenVerify = asyncService.tokenVerify(token);

        // token正常/过期，去取refreshToken
        CompletableFuture refreshVerify = asyncService.refreshVerify(refreshToken);

        // token和refreshToken对应的也必须是同一个人
        CompletableFuture<String> usernameFuture = asyncService.compareUsername(refreshToken, token);

        try {
            tokenVerify.join();
            refreshVerify.join();
            String phoneNumber = usernameFuture.join();

            // 验证通过，重新生成token和refreshToken
            createAuth(phoneNumber, response);

            return R.ok();
        }catch (CompletionException e){
            // 三个线程有一个报错都会直接抛异常，不会向下执行
            throw e.getCause();
        }
    }

    private void createAuth(String phoneNumber, HttpServletResponse response) {
        // 生成token,refreshToken
        String token = JWTUtil.generateToken(phoneNumber);
        String refreshToken = JWTUtil.generateRefreshToken(phoneNumber+"refreshToken");

        // 暴露出对应的响应头信息
        response.addHeader("Authorization", token);
        response.setHeader("refreshToken", refreshToken);
        response.setHeader("Access-Control-Expose-Headers", "Authorization,refreshToken");
    }
}
